/*
 * $Id: QELogDocument.m 248 2008-09-08 15:31:38Z jon $
 * Sleeper
 *
 * Created by Jon Gordon on 7/27/08
 * Copyright Jon Gordon.  All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining 
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including 
 * without limitation the rights to use, copy, modify, merge, publish, 
 * distribute, sublicense, and/or sell copies of the Software, and to 
 * permit persons to whom the Software is furnished to do so, subject to 
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */


#import "QELogDocument.h"

#import "QEArrlSection.h"
#import "QEContact.h"
#import "QEContestCategory.h"
#import "QEEntryData.h"
#import "QEOperator.h"


static NSString *MAIN_WINDOW_NIB_NAME = @"LogWindow";


@interface QELogDocument (private)
/*
 * Callback method for when display clock's timer fires.
 */
- (void)clockUpdate:(NSTimer *)theTimer;

/*
 * Update the sent exchange when one of the parameters changes
 */
- (void)updateSentExchange;

- (void)updateDupeCallWarning;


#pragma mark PRIMITIVE OPERATIONS
- (void)clearExchange;
- (void)restoreExchange:(NSString *)value;
- (void)createQsoWithNewExchange:(NSString *)newExchange;
- (void)uncreateQso:(QEContact *)qso fromExchange:(NSString *)src;

#pragma mark PRIVATE FIELDS
- (QELogWindowFields *)fields;
- (void)setFields:(QELogWindowFields *)newValue;

- (QELogModel *)model;
- (void)setModel:(QELogModel *)newValue;

- (QELogWindowController *)logWindow;
- (void)setLogWindow:(QELogWindowController *)newValue;
@end


@implementation QELogDocument
#pragma mark CREATION/DESTRUCTION
- (id)init
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    self = [super init];
    if (self) {
        [self setFields:[[[QELogWindowFields alloc] init] autorelease]];
        [self setModel:[[[QELogModel alloc] init] autorelease]];
        logWindow = nil;
    } // if
    return self;
} // init


- (void)dealloc
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    [self setFields:nil];
    [self setModel:nil];
    [self setLogWindow:nil];
    [super dealloc];
} // dealloc


#pragma mark FIELDS
- (BOOL)canDeleteOperator
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return canDeleteOperator;
} // canDeleteOperator


- (void)setCanDeleteOperator:(BOOL)newValue
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    canDeleteOperator = newValue;
} // setCanDeleteOperator:


#pragma mark OPERATIONS
- (IBAction)clearAction:(id)sender
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    [self clearExchange];
} // clearAction:


- (IBAction)enterAction:(id)sender
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    [self createQsoWithNewExchange:@""];
} // enterAction:


- (IBAction)exportCabrilloAction:(id)sender
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    NSSavePanel *savePanel = [NSSavePanel savePanel];
    SEL sel = @selector(exportPanelDidEnd:returnCode:contextInfo:);
    [savePanel beginSheetForDirectory:@"~/Documents"
                                 file:@"cabrillo.txt"
                       modalForWindow:[self windowForSheet]
                        modalDelegate:self
                       didEndSelector:sel
                          contextInfo:nil];
} // exportCabrilloAction:


- (void)exportPanelDidEnd:(NSSavePanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (NSOKButton == returnCode) {
        NSString *filename = [[sheet filename] stringByExpandingTildeInPath];
        NSLog(@"  exporting Cabrillo log to file \"%@\"", filename);
        NSString *source = [[self model] dataAsCabrilloText];
        [source writeToFile:filename atomically:YES encoding:NSASCIIStringEncoding error:NULL];
    } else {
        NSLog(@"  user cancelled Cabrillo log export");
    } // else
} // exportPanelDidEnd:returnCode:contextInfo:


- (IBAction)showSectionsAction:(id)sender {
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    // currently a nil operation
} // showSectionsAction:


- (IBAction)addOperatorAction:(id)sender
{
    /*
     * This method is adapted from chapter 9 of Hillegass, Cocoa 
     * Programming (3rd ed.).
     */
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    NSWindow *w = [operatorsTable window];
    
    // Try to end any editing that is taking place
    BOOL editingEnded = [w makeFirstResponder:w];
    if (!editingEnded) {
        NSLog(@"  WARNING: unable to end editing");
        return;
    } // if
    NSUndoManager *undo = [self undoManager];
    
    // Has an edit occurred already in this event?
    if ([undo groupingLevel]) {
        // Close the last group
        [undo endUndoGrouping];
        // Open a new group
        [undo beginUndoGrouping];
    } // if
    // Create the record for the new operator
    QEOperator *op = [operatorsController newObject];
    
    // Add it to the content array of 'operatorController'
    [operatorsController addObject:op];
    [op release];
    
    // Re-sort (in case the user has sorted a column)
    [operatorsController rearrangeObjects];
    
    // get the sorted array
    NSArray *a = [operatorsController arrangedObjects];
    
    // Find the object just added
    int row = [a indexOfObjectIdenticalTo:op];
    NSLog(@"  starting edit of %@ in row %d", op, row);
    
    // Begin the edit in the first (and only) column
    [operatorsTable editColumn:0
                           row:row
                     withEvent:nil
                        select:YES];
    
} // addOperatorAction:


- (IBAction)deleteOperatorAction:(id)sender
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    [operatorsController removeObjects:[operatorsController selectedObjects]];
} // deleteOperatorAction:


- (IBAction)editEntryInfo:(id)sender
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    tmpEntryData = [[[self model] entryData] copy];
    [entryDataController setContent:tmpEntryData];
    [NSApp beginSheet:entryDataSheet
       modalForWindow:[self windowForSheet]
        modalDelegate:nil
       didEndSelector:NULL
          contextInfo:NULL];
} // editEntryInfo:


- (IBAction)closeEntryDataSheet:(id)sender
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    [NSApp endSheet:entryDataSheet];
    [entryDataSheet orderOut:self];
    
    if ([[sender title] isEqual:@"OK"] && ![[[self model] entryData] isEqual:tmpEntryData]) {
        NSLog(@"  overwriting entry data");
        [entryDataController commitEditing];
        [[self model] setEntryData:tmpEntryData];
        [self updateChangeCount:NSChangeDone];
        [self updateSentExchange];
    } // if
    
    // clean up the entry data now, rather than wait until closing,
    // because I'm neurotic about memory leaks
    [entryDataController setContent:nil];
    [tmpEntryData release];
} // closeEntryDataSheet:


#pragma mark OVERRIDDEN METHODS
- (NSString *)windowNibName
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    // Override returning the nib file name of the document
    // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead.
    return MAIN_WINDOW_NIB_NAME;
} // windowNibName


- (void)makeWindowControllers
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    // Overridden to create window controller--and window--using QELogWindowController
    // instead of NSWindowController
    QELogWindowController *wc = [[QELogWindowController alloc] initWithWindowNibName:[self windowNibName]
                                                                               owner:self];
    [self addWindowController:wc];
    [self setLogWindow:wc];
    [wc release];
} // makeWindowControllers


- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if ([[aController windowNibName] isEqual:MAIN_WINDOW_NIB_NAME]) {
        [super windowControllerDidLoadNib:aController];
        
        NSLog(@"  nib loaded for main document window");
        // Add any code here that needs to be executed once the windowController has loaded the document's window.
        [fieldController setContent:[self fields]];
        [[aController window] setDelegate:self];
        
        // the log window has a UTC clock that updates in real time
        NSDateFormatter *timeFieldFormatter = [[[NSDateFormatter alloc] init] autorelease];
        [timeFieldFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
        [timeFieldFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
        [timeFieldFormatter setDateFormat:@"dd-MMM-yyyy HH:mm:ss 'UTC'"];
        [[timeField cell] setFormatter:timeFieldFormatter];
        [[self fields] setCurrentTime:[NSDate date]];
        
        // make sure that the contacts table also shows the time in UTC, too
        NSDateFormatter *timeColumnFormatter = [[[NSDateFormatter alloc] init] autorelease];
        [timeColumnFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
        [timeColumnFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
        [timeColumnFormatter setDateFormat:@"dd-MMM HH:mm:ss"];
        [[[contactsTable tableColumnWithIdentifier:@"time"] dataCell] setFormatter:timeColumnFormatter];    
        
        clockTimer = [NSTimer scheduledTimerWithTimeInterval:0.29
                                                      target:self
                                                    selector:@selector(clockUpdate:)
                                                    userInfo:nil
                                                     repeats:YES];
        
        // need to keep track of the operators controller's selection so we can tell
        // whether the delete operator button is enabled in the entry window
        [operatorsController addObserver:self 
                              forKeyPath:@"selectedObjects" 
                                 options:NSKeyValueObservingOptionNew 
                                 context:NULL];
        
        [self updateSentExchange];
    } else {
        NSLog(@"  WARNING: unexpected nib file loaded for window controller");
    } // else
} // windowControllerDidLoadNib:

- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    // Insert code here to write your document to data of the specified type. If the given outError != NULL, ensure that you set *outError when returning nil.
    
    // You can also choose to override -fileWrapperOfType:error:, -writeToURL:ofType:error:, or -writeToURL:ofType:forSaveOperation:originalContentsURL:error: instead.
    
    // For applications targeted for Panther or earlier systems, you should use the deprecated API -dataRepresentationOfType:. In this case you can also choose to override -fileWrapperRepresentationOfType: or -writeToFile:ofType: instead.
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:[self model]];    
    if (!data && (outError != NULL) ) {
		*outError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:nil];
    } // if
    return data;
} // dataOfType:error:


- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    // Insert code here to read your document from the given data of the specified type.  If the given outError != NULL, ensure that you set *outError when returning NO.
    
    // You can also choose to override -readFromFileWrapper:ofType:error: or -readFromURL:ofType:error: instead. 
    
    // For applications targeted for Panther or earlier systems, you should use the deprecated API -loadDataRepresentation:ofType. In this case you can also choose to override -readFromFile:ofType: or -loadFileWrapperRepresentation:ofType: instead.
    BOOL readSuccess = NO;
    QELogModel *fileContents = [NSKeyedUnarchiver unarchiveObjectWithData:data];
    if (nil != fileContents) {
        readSuccess = YES;
        [self setModel:fileContents];
    } else if ( outError != NULL ) {
		*outError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileReadUnknownError userInfo:nil];
    } // else if
    return readSuccess;
} // readFromData:ofType:error:


- (void)windowWillClose:(NSNotification *)notification
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    NSString *nibName = [[[notification object] windowController] windowNibName];
    if ([nibName isEqual:MAIN_WINDOW_NIB_NAME]) {
        // closing main document window
        NSLog(@"  closing main document window");
        [clockTimer invalidate];
        [self setLogWindow:nil];
    } else {
        NSLog(@"  WARNING: closing unrecognized window");
    } // else
} // close


- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if ([self fields] == object) {
        if ([keyPath isEqual:@"entrySrc"]) {
            id newValue = [change objectForKey:NSKeyValueChangeNewKey];
            if ([[NSNull null] isEqual:newValue]) {
                newValue = nil;
            } // if
            [[self model] setExchangeRcvd:newValue];
            [[self fields] setValuesForKeysWithDictionary:[[self model] allElements]];
            [self updateDupeCallWarning];
        } // if
    } else if (operatorsController == object) {
        if ([keyPath isEqual:@"selectedObjects"]) {
            [self setCanDeleteOperator:([[operatorsController selectedObjects] count] > 0)];
        } // if
    } // else if
} // observeValueForKeyPath:ofObject:change:context:
@end


@implementation QELogDocument (private)
- (void)clockUpdate:(NSTimer *)theTimer
{
    //    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    [[self fields] setCurrentTime:[NSDate date]];
} // clockUpdate:


- (void)updateSentExchange
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    
    static const NSString *CALL_PLACEHOLDER = @"<call>";
    static const NSString *CAT_PLACEHOLDER = @"<P>";
    static const NSString *CHECK_PLACEHOLDER = @"<check>";
    static const NSString *SECTION_PLACEHOLDER = @"<section>";
    
    QEEntryData *ed = [[self model] entryData];
    NSMutableString *buffer = [[NSMutableString alloc] init];
    
    if ((nil != [ed entryCallsign]) && (0 < [[ed entryCallsign] length])) {
        [buffer appendFormat:@"%@ ", [ed entryCallsign]];
    } else {
        [buffer appendFormat:@"%@ ", CALL_PLACEHOLDER];
    } // else
    
    [buffer appendFormat:@"%d ", [[self model] nextSerial]];
    
    if ((nil != [ed category]) && (0 < [[ed category] length])) {
        [buffer appendFormat:@"%@ ", [ed category]];
    } else { 
        [buffer appendFormat:@"%@ ", CAT_PLACEHOLDER];
    } // else
    
    if (nil != [ed check]) {
        [buffer appendFormat:@"%02d ", [[ed check] intValue]];
    } else {
        [buffer appendFormat:@"%@ ", CHECK_PLACEHOLDER];
    } // else
    
    if ((nil != [ed section]) && (0 < [[ed section] length])) {
        [buffer appendFormat:@"%@", [ed section]];
    } else {
        [buffer appendFormat:@"%@", SECTION_PLACEHOLDER];
    } // else
    
    [[self fields] setOutExchange:[NSString stringWithString:buffer]];
    [buffer release];
} // updateSentExchange


- (void)updateDupeCallWarning
{
    if ([[self model] isCallInLog:[[self fields] call]]) {
        [callField setBackgroundColor:[NSColor yellowColor]]; 
    } else {
        [callField setBackgroundColor:[NSColor textBackgroundColor]];
    } // else
} // updateDupeCallWarning


#pragma mark PRIMITIVE OPERATIONS
- (void)clearExchange
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    NSString *exchange = [[self fields] entrySrc];
    [[[self undoManager] prepareWithInvocationTarget:self] restoreExchange:exchange];
    [[self fields] setEntrySrc:@""];
    [[self undoManager] setActionName:@"Clear"];
} // clearExchange


- (void)restoreExchange:(NSString *)value
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    [[[self undoManager] prepareWithInvocationTarget:self] clearExchange];
    [[self fields] setEntrySrc:value];
    [[self undoManager] setActionName:@"Clear"];
} // restoreExchange:


- (void)createQsoWithNewExchange:(NSString *)newExchange
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    NSString *exchange = [[self fields] entrySrc];
    QEContact *newContact = [[self model] addContact];
    [[[self undoManager] prepareWithInvocationTarget:self] uncreateQso:newContact fromExchange:exchange];
    [[self undoManager] setActionName:@"Enter"];
    [[self fields] setEntrySrc:newExchange];
    [self updateSentExchange];
} // createQso


- (void)uncreateQso:(QEContact *)qso fromExchange:(NSString *)src
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    [[[self undoManager] prepareWithInvocationTarget:self] createQsoWithNewExchange:[[self fields] entrySrc]];
    [[self model] removeContact:qso];
    [[self fields] setEntrySrc:src];
    [[self undoManager] setActionName:@"Enter"];
    [self updateSentExchange];
} // uncreateQso:fromExchange:

- (QELogWindowFields *)fields
{
    //    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[fields retain] autorelease];
} // fields

- (void)setFields:(QELogWindowFields *)newValue
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (newValue != fields) {
        id temp = fields;
        fields = [newValue retain];
        if (temp) {
            [temp removeObserver:self
                      forKeyPath:@"entrySrc"];
            [temp release];
        } // if
        
        if (newValue) {
            [fields addObserver:self
                     forKeyPath:@"entrySrc"
                        options:NSKeyValueObservingOptionNew
                        context:NULL];
        } // if
    } // if
} // setFields:


- (QELogModel *)model
{
    //    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[model retain] autorelease];
} // model

- (void)setModel:(QELogModel *)newValue
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (newValue != model) {
        id temp = model;
        model = [newValue retain];
        [temp release];
    } // if
} // setModel:


- (QELogWindowController *)logWindow
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[logWindow retain] autorelease];
} // logWindow

- (void)setLogWindow:(QELogWindowController *)newValue
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (logWindow != newValue) {
        id tmp = logWindow;
        logWindow = [newValue retain];
        [tmp release];
    } // if
} // setLogWindow:
@end
